angular.module("simple.ssnau", [])
    .directive("showOnHoverParent", function () {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
        };

        return {
            link: function (scope, elem) {
                var ndElem = $(elem),
                    ndParent = ndElem.parent();

                if (!ndParent.attr("id")) ndParent.attr("id", "n" + s4() + s4() + s4());
                if (!ndElem.attr("id")) ndElem.attr("id", "n" + s4() + s4() + s4());

                var display = ndElem.css("display");
                var css = ('#==pid==:hover #==eid== {display: ==display== }\n ' +
                    '#==pid== #==eid== {display:none;}')
                    .replace(/==display==/g, display)
                    .replace(/==pid==/g, ndParent.attr("id"))
                    .replace(/==eid==/g, ndElem.attr("id"));

                var style = document.createElement('style');
                if (style.styleSheet)
                    style.styleSheet.cssText = css;
                else
                    style.appendChild(document.createTextNode(css));

                document.getElementsByTagName('head')[0].appendChild(style);
            }
        }
    })

/**
 * <div ace-editor="{name: 'markdownEditor', scroll: 'expression'}"/>, so $scope.markdownEditor = {{The editor instance}}
 */
    .directive("aceEditor", function () {
        return {
            require: '?ngModel',
            link: function (scope, elem, attr, ctrl) {
                if (typeof ace === undefined) {
                    alert("no ACE Editor found!!");
                    return;
                }

                var editor = ace.edit(elem[0]),
                    config = scope.$eval(attr.aceEditor);

                //Sets whether or not line wrapping is enabled
                editor.getSession().setUseWrapMode(true);
                editor.getSession().setUseSoftTabs(true);
                editor.setShowPrintMargin(false);

                var editorName = config.name;
                if (editorName) {
                    scope[editorName] = editor;
                }

                if (config.scroll) {
                    editor.getSession().on("changeScrollTop", _.debounce(function() {
                        if (angular.isFunction(config.scroll)) {
                            config.scroll();
                        } else {
                            scope.$eval(config.scroll);
                        }
                        scope.$apply();
                    }, 500));
                    editor.getSession().on("changeCursor", function() {
                        console.log('cursor gone...')
                    });
                    editor.getSession().on("changeSelection", function() {
                        console.log('selction gone...')
                    });

                }

                editor.getSession().setMode('ace/mode/markdown');
                editor.setTheme("ace/theme/monokai");

                editor.$notify = function() {
                    ctrl.$setViewValue(editor.getSession().getValue());
                    //发出事件到上级Scope,依赖于editorName
                    if (editorName) {
                        scope.$emit(editorName + "Change", editor.getSession().getUndoManager().$undoStack.length);
                    }
                    scope.$apply();
                };



                elem.on('keyup', 'textarea', function () {
                    editor.$notify();
                });

                ctrl.$render = function() {
                    editor.getSession().setValue((ctrl.$viewValue ? ctrl.$viewValue : ''));
                };

                ctrl.$setViewValue(editor.getSession().getValue());

                //enhancement API
                editor.getFirstVisibleRowOffsetPercent = function() {
                    var contentNode = elem.find(".ace_content"),
                        topOffset = parseFloat(contentNode.css("margin-top")),
                        firstRowNode = elem.find(".ace_content>.ace_text-layer>.ace_line_group").first();

                    if (!firstRowNode.length) return null;

                    var rowHeight = firstRowNode.outerHeight();

                    return Math.abs(topOffset) / rowHeight;
                }
            }
        }
    })
/**
 * Usage（如果我们希望一个按钮有dropdown menu,则紧随其后定义一个class为dropdown-menu的元素即可）:
 <div class="edit-box-tool-icon clickable" has-dropdown="">
    <i class="icon-beaker"></i>
 </div>
 <ul class="dropdown-menu">
    <li>1</li>
    <li>2</li>
    <li>3</li>
 </ul>
 */
    .directive("hasDropdown", function () {

        var dropdownMenus = [];

        var dropdownable = [];

        var lastOpen = null;

        function closeAll() {
            dropdownMenus.forEach(function (ndMenu) {
                ndMenu.hide();
            });
            dropdownable.forEach(function(dropdown){
                dropdown.removeClass("selected");
            });

            lastOpen = null;
        }

        var hasBind = false;
        function bindWindowEvent() {
            if (hasBind) return;
            $(document).on("click", function () {
                closeAll();
            });
            hasBind = true;
        }


        function positionMenu(elem, ndMenu){
            var pos = {left: elem.offset().left, top: elem.offset().top},
                dem = {width: elem.outerWidth(), height: elem.outerHeight()},
                limit = {width: $(window).width(), height: $(window).height()};

            var left, top, right, bottom, useBottom, useRight;

            left = pos.left;
            top = pos.top + dem.height;

            var mHeight = ndMenu.outerHeight(),
                mWidth  = ndMenu.outerWidth(),
                beyondBottom = mHeight + top - limit.height,
                beyondRight  = mWidth + left - limit.width;

            //1. 超下界
            if (beyondBottom > 0) {
                //如果超上界(或不超)超的比下界小，则采用上界
                if (mHeight - top < beyondBottom) {
                    bottom = limit.height - pos.top;
                    useBottom = true;
                }
            }

            //2. 超右界
            if (beyondRight > 0) {
                right = 0;
                useRight = true;
            }

            //set to auto first
            ndMenu.css("top", "auto");
            ndMenu.css("bottom", "auto");
            ndMenu.css("left", "auto");
            ndMenu.css("right", "auto");

            if (useBottom) {
                ndMenu.css("bottom", bottom + "px");
            } else {
                ndMenu.css("top", top + "px");
            }

            if (useRight) {
                ndMenu.css("right", right + "px");
            } else {
                ndMenu.css("left", left + "px");
            }

        }

        return {
            link: function (scope, elem, attr, ctrl) {
                var ndMenu = $(elem).next();

                if (!ndMenu.hasClass("dropdown-menu")) {
                    console.error("cannot found dropdown menu for has-dropdown element")
                }

                bindWindowEvent();

                ndMenu.css("position", "fixed");
                ndMenu.css("zIndex", 9999);
                dropdownMenus.push(ndMenu);
                dropdownable.push(elem);

                elem.on('click', function (e) {
                    e.stopPropagation();
                    e.preventDefault();

                    if (lastOpen == ndMenu) {
                        closeAll(); //cannot put it outside the if-statement, because closeAll() would change `lastOpen`
                        return;
                    }

                    closeAll();

                    $(elem).addClass("selected");

                    ndMenu.show();

                    positionMenu(elem, ndMenu);

                    lastOpen = ndMenu;
                });
            }
        }
    })

/**
 * config的参数：
 * onchange: 当内容改变时，触发的事件
 * onreset: 当内容恢复成初始的$viewValue时
 * max: 内容最大长度
 * singleLine: 是否是单行
 * enable: 监听变量，表示是否启用contenteditable
 * <span contenteditable="true" contenteditable-config="{onchange:[func|expression]}">
 */
    .directive('contenteditable', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                var editorName = attrs.contenteditableName,
                    config = scope.$eval(attrs.contenteditableConfig),
                    isSingleLine = config.singleLine,
                    maxLength = config.max - 0,
                    onchangeFunc = resolveFunc(config.onchange || angular.noop),
                    onresetFunc = resolveFunc(config.onreset || angular.noop),
                    enable = config.enable,
                    oriText = "",
                    dirty = false;

                function resolveFunc(f) {
                    return angular.isString(f) ? function(){
                        scope.$eval(f);
                    } : f;
                }

                //如果有设enable监听属性的话
                if (enable) {
                    scope.$watch(enable, function(v){
                        elm.attr('contenteditable', !!v);
                    })
                }

                //只有keydown才能阻止事件的发生
                elm.on('keydown', function(e) {
                    var keyCode = e.keyCode;

                    //Nothing happens for Up/Down/Left/Right
                    if (keyCode >=37 && keyCode <=40) { //UP,DOWN,LEFT,RIGHT
                        return;
                    }

                    //Enter表示确认，因为signleLine的enter没有其它意义
                    if (isSingleLine && keyCode == 13) {//Enter
                        elm.blur();
                        e.preventDefault();
                    }

                    // 如果有maxLength
                    // 1. 但仅Up/Down/Left/Right/Backspace/Delete有效
                    // 2. 组合键仍有效，因为用户可能想ctrl + A
                    // 3. 如果用户已经选中了文字，则表示要替换内容，此时放行 TODO: 是否考虑Mac的Command键
                    if (maxLength) {
                        //如果有selection.
                        var selection = window.getSelection();
                        if (selection && selection.type == "Range") {
                            return;
                        }
                        if (elm.text().length >= maxLength) {
                            if (keyCode != 8 && keyCode != 46 && e.ctrlKey == false) {//8->Backspace, 46->Delete
                                e.preventDefault();
                            }
                        }
                    }
                });

                // view -> model
                elm.on('keyup', function() {
                    var innerText = elm.text(),
                        text      = normalize(innerText);

                    // Will here run into preformance issue?
                    if (innerText != text) {
                        elm.text(text); //browser compatible?
                    }

                    dirty = text != oriText;

                    if (dirty){
                        onchangeFunc();
                    } else {
                        onresetFunc();
                    }
                    scope.$apply(function() {
                        ctrl.$setViewValue(text);
                    });
                });

                // model -> view
                ctrl.$render = function() {
                    oriText = ctrl.$viewValue ? ctrl.$viewValue : '';
                    dirty = false;
                    elm.html(oriText);
                };

                // load init value from DOM
                var initText = elm.text();
                if (initText) {
                    oriText = normalize(initText);
                    ctrl.$setViewValue(oriText);
                }


                function normalize(text) {
                    var res = text;
                    if (isSingleLine) {
                        res = text.split("\n")[0];
                    }

                    if (maxLength) {
                        res = text.substr(0, maxLength);
                    }
                    return res;
                }
            }
        };
    })
/**
 * config的参数：
 * target: required target应该是一个selector或一个数字，表示要scroll过去的目标/坐标
 * offset: optional 表示在target的基础上的偏移量，可接收数字或百分比。
 *         如果是数字的话，则offset即为数字本身。
 *         如果是百分比，则offset为target.height() * 百分比
 *                     如果target为数字的话，则 target * 百分比
 *
 * <div scroll-top="{target:selectedId, animate: speed}">
 */
.directive("scrollTop", function(){
        return {
            link: function(scope, elm, attrs, ctrl) {
                var config = scope.$eval(attrs.scrollTop);

                if (config.target || config.offset) {
                    var key = config.target;
                    key += '+' + (config.offset || '" "');
                    scope.$watch(key, function() {
                        var selector = scope[config.target],
                            offset = (config.offset && scope[config.offset]) || 0,
                            pTop = elm.offset().top,
                            ndTarget = (typeof selector === "string") && elm.find(selector),
                            dest;

                        var cTop = (ndTarget.length && ndTarget.offset().top),
                            sTop = elm.scrollTop();


                        if (cTop) {//表示传入来的是Selector
                            dest = cTop-pTop+sTop;
                        } else {
                            //传进来的是一个绝对值
                            dest = selector;
                        }


                        // if offset is percent, convert it to number base on target
                        if (/%$/.test(offset + '')) {
                            // 正负号还会保留
                            offset = parseFloat(offset) / 100;
                            if (ndTarget.length) {
                                offset = ndTarget.outerHeight() * offset;
                            } else {
                                offset = ndTarget * offset;
                            }
                        }

                        if (!isNaN(dest)) {
                            elm.animate({scrollTop:dest + offset}, '500');
                        }
                    });
                }
            }
        }
    })
.directive("scroll", ['$parse', function($parse){
        return function ($scope, elm, attrs) {
            var fn = $scope.$eval(attrs.scroll);
            elm.bind("scroll", _.debounce(function (evt) {
                fn(evt, elm);
                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            }, 300));
        };
    }])